# Copyright (c) 2011 The Chromium Embedded Framework Authors. All rights
# reserved. Use of this source code is governed by a BSD-style license that
# can be found in the LICENSE file.

from __future__ import absolute_import
from cef_parser import *
import copy
import functools


def make_cpptoc_impl_proto(name, func, parts):
  proto = ''
  if func.has_attrib('no_stack_protector'):
    proto += 'NO_STACK_PROTECTOR '
  if isinstance(func, obj_function_virtual):
    proto += parts['retval'] + ' CEF_CALLBACK'
  else:
    proto += 'CEF_EXPORT ' + parts['retval']

  proto += ' ' + name + '(' + ', '.join(parts['args']) + ')'
  return proto


def make_cpptoc_function_impl_existing(cls, name, func, impl, defined_names,
                                       version, version_finder):
  notify(name + ' has manual edits')

  # retrieve the C API prototype parts
  parts = func.get_capi_parts(
      defined_names, True, version=version, version_finder=version_finder)

  changes = format_translation_changes(impl, parts)
  if len(changes) > 0:
    notify(name + ' prototype changed')

  return make_cpptoc_impl_proto(name, func,
                                parts) + '{' + changes + impl['body'] + '\n}\n'


def make_cpptoc_function_impl_new(cls, name, func, defined_names, base_scoped,
                                  version, version_finder):
  if not version is None and isinstance(func, obj_function_virtual) and \
    not func.exists_at_version(version):
    raise Exception(
        'Attempting to generate non-existing function %s at version %d' %
        (name, version))

  # Special handling for the cef_shutdown global function.
  is_cef_shutdown = name == 'cef_shutdown' and isinstance(
      func.parent, obj_header)

  # retrieve the C API prototype parts
  parts = func.get_capi_parts(
      defined_names, True, version=version, version_finder=version_finder)
  result = make_cpptoc_impl_proto(name, func, parts) + ' {'

  if isinstance(func.parent, obj_class) and \
      not func.parent.has_attrib('no_debugct_check') and \
      not base_scoped:
    result += '\n  shutdown_checker::AssertNotShutdown();\n'

  invalid = []

  # retrieve the function arguments
  args = func.get_arguments()

  # determine the argument types
  for arg in args:
    if arg.get_arg_type() == 'invalid':
      invalid.append(arg.get_name())

  # retrieve the function return value
  retval = func.get_retval()
  retval_type = retval.get_retval_type()
  if retval_type == 'invalid':
    invalid.append('(return value)')
    retval_default = ''
  else:
    retval_default = retval.get_retval_default(True)
    if len(retval_default) > 0:
      retval_default = ' ' + retval_default

  if len(invalid) > 0:
    notify(name + ' could not be autogenerated')
    # code could not be auto-generated
    result += '\n  // BEGIN DELETE BEFORE MODIFYING'
    result += '\n  // AUTO-GENERATED CONTENT'
    result += '\n  // COULD NOT IMPLEMENT DUE TO: ' + ', '.join(invalid)
    result += '\n  #pragma message("Warning: " __FILE__ ": ' + name + ' is not implemented")'
    result += '\n  // END DELETE BEFORE MODIFYING'
    result += '\n}\n\n'
    return result

  result += '\n  // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING\n'

  result_len = len(result)

  optional = []

  # parameter verification
  if isinstance(func, obj_function_virtual):
    result += '\n  DCHECK(self);'\
              '\n  if (!self) {'\
              '\n    return'+retval_default+';'\
              '\n  }'

  for arg in args:
    arg_type = arg.get_arg_type()
    arg_name = arg.get_type().get_name()

    # skip optional params
    optional_params = arg.parent.get_attrib_list('optional_param')
    if not optional_params is None and arg_name in optional_params:
      optional.append(arg_name)
      continue

    comment = '\n  // Verify param: ' + arg_name + '; type: ' + arg_type

    if arg_type == 'simple_byref' or arg_type == 'simple_byref_const' or \
       arg_type == 'simple_byaddr' or arg_type == 'bool_byref' or arg_type == 'bool_byaddr' or \
       arg_type == 'struct_byref_const' or arg_type == 'struct_byref' or \
       arg_type == 'string_byref_const' or arg_type == 'string_byref' or \
       arg_type == 'refptr_same' or arg_type == 'refptr_same_byref' or \
       arg_type == 'refptr_diff' or arg_type == 'refptr_diff_byref' or \
       arg_type == 'ownptr_same' or arg_type == 'ownptr_same_byref' or \
       arg_type == 'ownptr_diff' or arg_type == 'ownptr_diff_byref' or \
       arg_type == 'rawptr_same' or arg_type == 'rawptr_same_byref' or \
       arg_type == 'rawptr_diff' or arg_type == 'rawptr_diff_byref' or \
       arg_type == 'string_vec_byref' or arg_type == 'string_vec_byref_const' or \
       arg_type == 'string_map_single_byref' or arg_type == 'string_map_single_byref_const' or \
       arg_type == 'string_map_multi_byref' or arg_type == 'string_map_multi_byref_const':
      result += comment+\
                '\n  DCHECK('+arg_name+');'\
                '\n  if (!'+arg_name+') {'\
                '\n    return'+retval_default+';'\
                '\n  }'
      if arg_type == 'struct_byref_const' or arg_type == 'struct_byref':
        result +=\
                '\n  if (!template_util::has_valid_size('+arg_name+')) {'\
                '\n    DCHECK(false) << "invalid '+arg_name+'->[base.]size";'\
                '\n    return'+retval_default+';'\
                '\n  }'
    elif arg_type == 'simple_vec_byref' or arg_type == 'bool_vec_byref' or \
        arg_type == 'refptr_vec_same_byref' or arg_type == 'refptr_vec_diff_byref' or \
        arg_type == 'ownptr_vec_same_byref' or arg_type == 'ownptr_vec_diff_byref' or \
        arg_type == 'rawptr_vec_same_byref' or arg_type == 'rawptr_vec_diff_byref':
      result += comment+\
                '\n  DCHECK('+arg_name+'Count && (*'+arg_name+'Count == 0 || '+arg_name+'));'\
                '\n  if (!'+arg_name+'Count || (*'+arg_name+'Count > 0 && !'+arg_name+')) {'\
                '\n    return'+retval_default+';'\
                '\n  }'
    elif arg_type == 'simple_vec_byref_const' or arg_type == 'bool_vec_byref_const' or \
        arg_type == 'refptr_vec_same_byref_const' or arg_type == 'refptr_vec_diff_byref_const' or \
        arg_type == 'ownptr_vec_same_byref_const' or arg_type == 'ownptr_vec_diff_byref_const' or \
        arg_type == 'rawptr_vec_same_byref_const' or arg_type == 'rawptr_vec_diff_byref_const':
      result += comment+\
                '\n  DCHECK('+arg_name+'Count == 0 || '+arg_name+');'\
                '\n  if ('+arg_name+'Count > 0 && !'+arg_name+') {'\
                '\n    return'+retval_default+';'\
                '\n  }'

    # check index params
    index_params = arg.parent.get_attrib_list('index_param')
    if not index_params is None and arg_name in index_params:
      result += comment+\
                '\n  DCHECK_GE('+arg_name+', 0);'\
                '\n  if ('+arg_name+' < 0) {'\
                '\n    return'+retval_default+';'\
                '\n  }'

  if len(optional) > 0:
    # Wrap the comment at 80 characters.
    str = '\n  // Unverified params: ' + optional[0]
    for name in optional[1:]:
      str += ','
      if len(str) + len(name) + 1 > 80:
        result += str
        str = '\n  //'
      str += ' ' + name
    result += str

  if len(result) != result_len:
    result += '\n'
  result_len = len(result)

  # parameter translation
  params = []

  for arg in args:
    arg_type = arg.get_arg_type()
    arg_name = arg.get_type().get_name()

    comment = '\n  // Translate param: ' + arg_name + '; type: ' + arg_type

    if arg_type == 'simple_byval' or arg_type == 'simple_byaddr':
      params.append(arg_name)
    elif arg_type == 'simple_byref' or arg_type == 'simple_byref_const':
      data_type = arg.get_type().get_type()
      default = arg.get_type().get_result_simple_default()
      result += comment+\
                '\n  '+data_type+' '+arg_name+'Val = '+arg_name+'?*'+arg_name+':'+default+';'
      params.append(arg_name + 'Val')
    elif arg_type == 'bool_byval':
      params.append(arg_name + '?true:false')
    elif arg_type == 'bool_byref' or arg_type == 'bool_byaddr':
      result += comment+\
                '\n  bool '+arg_name+'Bool = ('+arg_name+' && *'+arg_name+')?true:false;'
      if arg_type == 'bool_byref':
        params.append(arg_name + 'Bool')
      else:
        params.append('&' + arg_name + 'Bool')
    elif arg_type == 'struct_byref_const':
      struct_type = arg.get_type().get_type()
      result += comment+\
                '\n  '+struct_type+' '+arg_name+'Obj;'\
                '\n  if ('+arg_name+') {'\
                '\n    '+arg_name+'Obj.Set(*'+arg_name+', false);'\
                '\n  }'
      params.append(arg_name + 'Obj')
    elif arg_type == 'struct_byref':
      struct_type = arg.get_type().get_type()
      result += comment+\
                '\n  '+struct_type+' '+arg_name+'Obj;'\
                '\n  if ('+arg_name+') {'\
                '\n    '+arg_name+'Obj.AttachTo(*'+arg_name+');'\
                '\n  }'
      params.append(arg_name + 'Obj')
    elif arg_type == 'string_byref_const':
      params.append('CefString(' + arg_name + ')')
    elif arg_type == 'string_byref':
      result += comment+\
                '\n  CefString '+arg_name+'Str('+arg_name+');'
      params.append(arg_name + 'Str')
    elif arg_type == 'refptr_same' or arg_type == 'refptr_diff':
      ptr_class = arg.get_type().get_ptr_type()
      if arg_type == 'refptr_same':
        params.append(ptr_class + 'CppToC_Unwrap(' + arg_name + ')')
      else:
        params.append(ptr_class + 'CToCpp_Wrap(' + arg_name + ')')
    elif arg_type == 'ownptr_same' or arg_type == 'rawptr_same':
      ptr_class = arg.get_type().get_ptr_type()
      if arg_type == 'ownptr_same':
        params.append(ptr_class + 'CppToC_UnwrapOwn(' + arg_name + ')')
      else:
        params.append(ptr_class + 'CppToC_UnwrapRaw(' + arg_name + ')')
    elif arg_type == 'ownptr_diff' or arg_type == 'rawptr_diff':
      ptr_class = arg.get_type().get_ptr_type()
      result += comment+\
                '\n  CefOwnPtr<'+ptr_class+'> '+arg_name+'Ptr('+ptr_class+'CToCpp_Wrap('+arg_name+'));'
      if arg_type == 'ownptr_diff':
        params.append('std::move(' + arg_name + 'Ptr)')
      else:
        params.append(arg_name + 'Ptr.get()')
    elif arg_type == 'refptr_same_byref' or arg_type == 'refptr_diff_byref':
      ptr_class = ptr_class_u = arg.get_type().get_ptr_type()
      if arg_type == 'refptr_same_byref':
        assign = ptr_class + 'CppToC_Unwrap(*' + arg_name + ')'
      else:
        assign = ptr_class + 'CToCpp_Wrap(*' + arg_name + ')'
      result += comment+\
                '\n  CefRefPtr<'+ptr_class_u+'> '+arg_name+'Ptr;'\
                '\n  if ('+arg_name+' && *'+arg_name+') {'\
                '\n    '+arg_name+'Ptr = '+assign+';'\
                '\n  }'\
                '\n  '+ptr_class+'* '+arg_name+'Orig = '+arg_name+'Ptr.get();'
      params.append(arg_name + 'Ptr')
    elif arg_type == 'string_vec_byref' or arg_type == 'string_vec_byref_const':
      result += comment+\
                '\n  std::vector<CefString> '+arg_name+'List;'\
                '\n  transfer_string_list_contents('+arg_name+', '+arg_name+'List);'
      params.append(arg_name + 'List')
    elif arg_type == 'string_map_single_byref' or arg_type == 'string_map_single_byref_const':
      result += comment+\
                '\n  std::map<CefString, CefString> '+arg_name+'Map;'\
                '\n  transfer_string_map_contents('+arg_name+', '+arg_name+'Map);'
      params.append(arg_name + 'Map')
    elif arg_type == 'string_map_multi_byref' or arg_type == 'string_map_multi_byref_const':
      result += comment+\
                '\n  std::multimap<CefString, CefString> '+arg_name+'Multimap;'\
                '\n  transfer_string_multimap_contents('+arg_name+', '+arg_name+'Multimap);'
      params.append(arg_name + 'Multimap')
    elif arg_type == 'simple_vec_byref' or arg_type == 'bool_vec_byref' or \
        arg_type == 'refptr_vec_same_byref' or arg_type == 'refptr_vec_diff_byref':
      vec_type = arg.get_type().get_vector_type()
      if arg_type == 'simple_vec_byref':
        assign = arg_name + '[i]'
      elif arg_type == 'bool_vec_byref':
        assign = arg_name + '[i]?true:false'
      elif arg_type == 'refptr_vec_same_byref':
        ptr_class = arg.get_type().get_ptr_type()
        assign = ptr_class + 'CppToC_Unwrap(' + arg_name + '[i])'
      elif arg_type == 'refptr_vec_diff_byref':
        ptr_class = arg.get_type().get_ptr_type()
        assign = ptr_class + 'CToCpp_Wrap(' + arg_name + '[i])'
      result += comment+\
                '\n  std::vector<'+vec_type+' > '+arg_name+'List;'\
                '\n  if ('+arg_name+'Count && *'+arg_name+'Count > 0 && '+arg_name+') {'\
                '\n    for (size_t i = 0; i < *'+arg_name+'Count; ++i) {'\
                '\n      '+arg_name+'List.push_back('+assign+');'\
                '\n    }'\
                '\n  }'
      params.append(arg_name + 'List')
    elif arg_type == 'simple_vec_byref_const' or arg_type == 'bool_vec_byref_const' or \
        arg_type == 'refptr_vec_same_byref_const' or arg_type == 'refptr_vec_diff_byref_const' or \
        arg_type == 'rawptr_vec_same_byref_const' or arg_type == 'rawptr_vec_diff_byref_const':
      vec_type = arg.get_type().get_vector_type()
      if arg_type == 'simple_vec_byref_const':
        assign = arg_name + '[i]'
      elif arg_type == 'bool_vec_byref_const':
        assign = arg_name + '[i]?true:false'
      else:
        ptr_class = arg.get_type().get_ptr_type()
        if arg_type == 'refptr_vec_same_byref_const':
          assign = ptr_class + 'CppToC_Unwrap(' + arg_name + '[i])'
        elif arg_type == 'refptr_vec_diff_byref_const':
          assign = ptr_class + 'CToCpp_Wrap(' + arg_name + '[i])'
        elif arg_type == 'rawptr_vec_same_byref_const':
          assign = ptr_class + 'CppToC_UnwrapRaw(' + arg_name + '[i])'
        elif arg_type == 'rawptr_vec_diff_byref_const':
          assign = ptr_class + 'CToCpp_Wrap(' + arg_name + '[i]).release()'
      result += comment+\
                '\n  std::vector<'+vec_type+' > '+arg_name+'List;'\
                '\n  if ('+arg_name+'Count > 0) {'\
                '\n    for (size_t i = 0; i < '+arg_name+'Count; ++i) {'\
                '\n      '+vec_type+' '+arg_name+'Val = '+assign+';'\
                '\n      '+arg_name+'List.push_back('+arg_name+'Val);'\
                '\n    }'\
                '\n  }'
      params.append(arg_name + 'List')
    else:
      raise Exception('Unsupported argument type %s for parameter %s in %s' %
                      (arg_type, arg_name, name))

  if len(result) != result_len:
    result += '\n'
  result_len = len(result)

  # execution
  result += '\n  // Execute\n  '

  if retval_type != 'none':
    # has a return value
    if retval_type == 'simple' or retval_type == 'simple_byaddr':
      result += retval.get_type().get_result_simple_type()
    else:
      result += retval.get_type().get_type()
    result += ' _retval = '

  if isinstance(func.parent, obj_class):
    # virtual and static class methods
    if isinstance(func, obj_function_virtual):
      ptr_class = cls.get_name()
      if not version_finder is None:
        ptr_class = version_finder(ptr_class, as_cpp=True)
      if cls.get_name() == func.parent.get_name():
        # virtual method called for the current class
        result += ptr_class + 'CppToC::Get(self)->'
      else:
        # virtual method called for a parent class
        result += ptr_class + 'CppToC::Get(reinterpret_cast<' + cls.get_capi_name(
            version) + '*>(self))->'
    else:
      result += func.parent.get_name() + '::'
  result += func.get_name() + '('

  if len(params) > 0:
    result += '\n      ' + ',\n      '.join(params)

  result += ');\n'

  if is_cef_shutdown:
    result += '\n\n#if DCHECK_IS_ON()'\
              '\n  shutdown_checker::SetIsShutdown();'\
              '\n#endif\n'

  result_len = len(result)

  # parameter restoration
  for arg in args:
    arg_type = arg.get_arg_type()
    arg_name = arg.get_type().get_name()

    comment = '\n  // Restore param: ' + arg_name + '; type: ' + arg_type

    if arg_type == 'simple_byref':
      result += comment+\
                '\n  if ('+arg_name+') {'\
                '\n    *'+arg_name+' = '+arg_name+'Val;'\
                '\n  }'
    elif arg_type == 'bool_byref' or arg_type == 'bool_byaddr':
      result += comment+\
                '\n  if ('+arg_name+') {'\
                '\n    *'+arg_name+' = '+arg_name+'Bool?true:false;'\
                '\n  }'
    elif arg_type == 'struct_byref':
      result += comment+\
                '\n  if ('+arg_name+') {'\
                '\n    '+arg_name+'Obj.DetachTo(*'+arg_name+');'\
                '\n  }'
    elif arg_type == 'refptr_same_byref' or arg_type == 'refptr_diff_byref':
      ptr_class = arg.get_type().get_ptr_type()
      if arg_type == 'refptr_same_byref':
        assign = ptr_class + 'CppToC_Wrap(' + arg_name + 'Ptr)'
      else:
        assign = ptr_class + 'CToCpp_Unwrap(' + arg_name + 'Ptr)'
      result += comment+\
                '\n  if ('+arg_name+') {'\
                '\n    if ('+arg_name+'Ptr.get()) {'\
                '\n      if ('+arg_name+'Ptr.get() != '+arg_name+'Orig) {'\
                '\n        *'+arg_name+' = '+assign+';'\
                '\n      }'\
                '\n    } else {'\
                '\n      *'+arg_name+' = nullptr;'\
                '\n    }'\
                '\n  }'
    elif arg_type == 'string_vec_byref':
      result += comment+\
                '\n  cef_string_list_clear('+arg_name+');'\
                '\n  transfer_string_list_contents('+arg_name+'List, '+arg_name+');'
    elif arg_type == 'string_map_single_byref':
      result += comment+\
                '\n  cef_string_map_clear('+arg_name+');'\
                '\n  transfer_string_map_contents('+arg_name+'Map, '+arg_name+');'
    elif arg_type == 'string_map_multi_byref':
      result += comment+\
                '\n  cef_string_multimap_clear('+arg_name+');'\
                '\n  transfer_string_multimap_contents('+arg_name+'Multimap, '+arg_name+');'
    elif arg_type == 'simple_vec_byref' or arg_type == 'bool_vec_byref' or \
        arg_type == 'refptr_vec_same_byref' or arg_type == 'refptr_vec_diff_byref':
      if arg_type == 'simple_vec_byref' or arg_type == 'bool_vec_byref':
        assign = arg_name + 'List[i]'
      elif arg_type == 'refptr_vec_same_byref':
        ptr_class = arg.get_type().get_ptr_type()
        assign = ptr_class + 'CppToC_Wrap(' + arg_name + 'List[i])'
      elif arg_type == 'refptr_vec_diff_byref':
        ptr_class = arg.get_type().get_ptr_type()
        assign = ptr_class + 'CToCpp_Unwrap(' + arg_name + 'List[i])'
      result += comment+\
                '\n  if ('+arg_name+'Count && '+arg_name+') {'\
                '\n    *'+arg_name+'Count = std::min('+arg_name+'List.size(), *'+arg_name+'Count);'\
                '\n    if (*'+arg_name+'Count > 0) {'\
                '\n      for (size_t i = 0; i < *'+arg_name+'Count; ++i) {'\
                '\n        '+arg_name+'[i] = '+assign+';'\
                '\n      }'\
                '\n    }'\
                '\n  }'
    elif arg_type == 'rawptr_vec_diff_byref_const':
      result += comment+\
                '\n  if ('+arg_name+'Count > 0) {'\
                '\n    for (size_t i = 0; i < '+arg_name+'Count; ++i) {'\
                '\n      delete '+arg_name+'List[i];'\
                '\n    }'\
                '\n  }'

  if len(result) != result_len:
    result += '\n'
  result_len = len(result)

  if len(result) != result_len:
    result += '\n'
  result_len = len(result)

  # return translation
  if retval_type != 'none':
    # has a return value
    result += '\n  // Return type: ' + retval_type
    if retval_type == 'simple' or retval_type == 'simple_byaddr' or retval_type == 'bool':
      result += '\n  return _retval;'
    elif retval_type == 'string':
      result += '\n  return _retval.DetachToUserFree();'
    elif retval_type == 'refptr_same':
      ptr_class = retval.get_type().get_ptr_type()
      result += '\n  return ' + ptr_class + 'CppToC_Wrap(_retval);'
    elif retval_type == 'refptr_diff':
      ptr_class = retval.get_type().get_ptr_type()
      result += '\n  return ' + ptr_class + 'CToCpp_Unwrap(_retval);'
    elif retval_type == 'ownptr_same':
      ptr_class = retval.get_type().get_ptr_type()
      result += '\n  return ' + ptr_class + 'CppToC_WrapOwn(std::move(_retval));'
    elif retval_type == 'ownptr_diff':
      ptr_class = retval.get_type().get_ptr_type()
      result += '\n  return ' + ptr_class + 'CToCpp_UnwrapOwn(std::move(_retval));'
    else:
      raise Exception('Unsupported return type %s in %s' % (retval_type, name))

  if len(result) != result_len:
    result += '\n'

  result += '}\n'
  return result


def make_cpptoc_function_impl(cls, funcs, existing, prefixname, suffixname,
                              defined_names, base_scoped, version,
                              version_finder):
  impl = ''

  customized = False

  for func in funcs:
    if not version is None and isinstance(func, obj_function_virtual) and \
      not func.exists_at_version(version):
      continue

    name = func.get_capi_name()
    if not prefixname is None:
      name = prefixname + '_' + name
    if not suffixname is None:
      name += '_' + suffixname

    if version is None:
      pre, post = get_version_surround(func, long=True)
    else:
      pre = post = ''

    value = get_next_function_impl(existing, name)
    if not value is None \
        and value['body'].find('// AUTO-GENERATED CONTENT') < 0:
      # an implementation exists that was not auto-generated
      customized = True
      impl += pre + make_cpptoc_function_impl_existing(
          cls, name, func, value, defined_names, version,
          version_finder) + post + '\n'
    else:
      impl += pre + make_cpptoc_function_impl_new(
          cls, name, func, defined_names, base_scoped, version,
          version_finder) + post + '\n'

  if not customized and impl.find('// COULD NOT IMPLEMENT') >= 0:
    customized = True

  return (impl, customized)


def make_cpptoc_virtual_function_impl(header, cls, existing, prefixname,
                                      suffixname, defined_names, base_scoped,
                                      version, version_finder):
  # don't modify the original list
  funcs = copy.copy(cls.get_virtual_funcs(version=version))
  cur_cls = cls
  while True:
    parent_name = cur_cls.get_parent_name()
    if is_base_class(parent_name):
      break
    else:
      parent_cls = header.get_class(parent_name)
      if parent_cls is None:
        raise Exception('Class does not exist: ' + parent_name)
      defined_names.append(parent_cls.get_capi_name(version))
      funcs.extend(parent_cls.get_virtual_funcs(version=version))
    cur_cls = header.get_class(parent_name)
    defined_names.append(cur_cls.get_capi_name(version))

  return make_cpptoc_function_impl(cls, funcs, existing, prefixname, suffixname,
                                   defined_names, base_scoped, version,
                                   version_finder)


def make_cpptoc_virtual_function_assignment_block(cls, offset, prefixname,
                                                  suffixname, version):
  impl = ''

  funcs = cls.get_virtual_funcs(version_order=True, version=version)

  for func in funcs:
    # should only include methods directly declared in the current class
    assert func.parent.get_name() == cls.get_name(), func.get_name()
    if version is None:
      pre, post = get_version_surround(func)
    else:
      if func.removed_at_version(version):
        continue
      pre = post = ''
    name = oname = func.get_capi_name()
    if not prefixname is None:
      name = prefixname + '_' + name
    if not suffixname is None:
      name += '_' + suffixname
    impl += pre + '  GetStruct()->' + offset + oname + ' = ' + name + ';\n' + post
  return impl


def make_cpptoc_virtual_function_assignment(header, cls, prefixname, suffixname,
                                            defined_names, version,
                                            version_finder):
  impl = make_cpptoc_virtual_function_assignment_block(cls, '', prefixname,
                                                       suffixname, version)

  cur_cls = cls
  offset = ''
  while True:
    parent_name = cur_cls.get_parent_name()
    offset += 'base.'
    if is_base_class(parent_name):
      break
    else:
      parent_cls = header.get_class(parent_name)
      if parent_cls is None:
        raise Exception('Class does not exist: ' + parent_name)
      defined_names.append(parent_cls.get_capi_name(version))
      impl += make_cpptoc_virtual_function_assignment_block(
          parent_cls, offset, prefixname, suffixname, version)
    cur_cls = header.get_class(parent_name)
    defined_names.append(cur_cls.get_capi_name(version))

  return impl


def make_cpptoc_unwrap_derived(header, cls, base_scoped, version):
  # identify all classes that derive from cls
  derived_classes = []
  cur_clsname = cls.get_name()
  allclasses = header.get_classes()
  for cur_cls in allclasses:
    if cur_cls.get_name() == cur_clsname:
      continue
    if cur_cls.has_parent(cur_clsname):
      derived_classes.append(cur_cls.get_name())

  derived_classes = sorted(derived_classes)

  if base_scoped:
    impl = ['', '']
    for clsname in derived_classes:
      derived_cls = header.get_class(clsname)
      if version is None:
        capiname = derived_cls.get_capi_name()
        pre, post = get_version_surround(derived_cls)
      else:
        if not derived_cls.exists_at_version(version):
          continue
        capiname = derived_cls.get_capi_name(first_version=True)
        pre = post = ''

      impl[0] += pre + '  if (type == '+get_wrapper_type_enum(clsname)+') {\n'+\
                 '    return '+clsname+'CppToC_UnwrapOwn(reinterpret_cast<'+capiname+'*>(s));\n'+\
                 '  }\n' + post
      impl[1] += pre + '  if (type == '+get_wrapper_type_enum(clsname)+') {\n'+\
                 '    return '+clsname+'CppToC_UnwrapRaw(reinterpret_cast<'+capiname+'*>(s));\n'+\
                 '  }\n' + post
  else:
    impl = ''
    for clsname in derived_classes:
      derived_cls = header.get_class(clsname)
      if version is None:
        capiname = derived_cls.get_capi_name()
        pre, post = get_version_surround(derived_cls)
      else:
        if not derived_cls.exists_at_version(version):
          continue
        capiname = derived_cls.get_capi_name(first_version=True)
        pre = post = ''

      impl += pre + '  if (type == '+get_wrapper_type_enum(clsname)+') {\n'+\
              '    return '+clsname+'CppToC_Unwrap(reinterpret_cast<'+capiname+'*>(s));\n'+\
              '  }\n' + post
  return impl


def make_cpptoc_version_wrappers(header, cls, base_scoped, versions):
  assert len(versions) > 0

  clsname = cls.get_name()
  typename = clsname + 'CppToC'
  structname = cls.get_capi_name(version=versions[0])

  rversions = sorted(versions, reverse=True)

  notreached = format_notreached(
      True,
      '" called with invalid version " << version',
      default_retval='nullptr')

  impl = ''

  if base_scoped:
    impl += structname + '* ' + typename + '_WrapOwn(CefOwnPtr<' + clsname + '> c) {\n' + \
            '  const int version = cef_api_version();\n'

    for version in rversions:
      vstr = str(version)
      impl += '  if (version >= ' + vstr + ') {\n'
      if versions[0] == version:
        impl += '    return ' + clsname + '_' + vstr + '_CppToC::WrapOwn(std::move(c));\n'
      else:
        impl += '    return reinterpret_cast<' + structname + '*>(' + clsname + '_' + vstr + '_CppToC::WrapOwn(std::move(c)));\n'
      impl += '  }\n'

    impl += '  ' + notreached + '\n'+ \
            '}\n\n' + \
            'std::pair<CefOwnPtr<CefBaseScoped>, '+ structname + '*> ' + typename + '_WrapRaw(CefRawPtr<' + clsname + '> c) {\n' + \
            '  const int version = cef_api_version();\n'

    for version in rversions:
      vstr = str(version)
      impl += '  if (version >= ' + vstr + ') {\n'
      if versions[0] == version:
        impl += '    return ' + clsname + '_' + vstr + '_CppToC::WrapRaw(std::move(c));\n'
      else:
        impl += '    auto [ownPtr, structPtr] = ' + clsname + '_' + vstr + '_CppToC::WrapRaw(std::move(c));\n' + \
                '    return std::make_pair(std::move(ownPtr), reinterpret_cast<' + structname + '*>(structPtr));\n'
      impl += '  }\n'

    impl += '  ' + notreached + '\n'+ \
            '}\n\n' + \
            'CefOwnPtr<' + clsname + '> ' + typename + '_UnwrapOwn('+ structname + '* s) {\n' + \
            '  const int version = cef_api_version();\n'

    for version in rversions:
      vstr = str(version)
      impl += '  if (version >= ' + vstr + ') {\n'
      if versions[0] == version:
        impl += '    return ' + clsname + '_' + vstr + '_CppToC::UnwrapOwn(s);\n'
      else:
        impl += '    return ' + clsname + '_' + vstr + '_CppToC::UnwrapOwn(reinterpret_cast<' + cls.get_capi_name(
            version) + '*>(s));\n'
      impl += '  }\n'

    impl += '  ' + notreached + '\n'+ \
            '}\n\n' + \
            'CefRawPtr<' + clsname + '> ' + typename + '_UnwrapRaw('+ structname + '* s) {\n' + \
            '  const int version = cef_api_version();\n'

    for version in rversions:
      vstr = str(version)
      impl += '  if (version >= ' + vstr + ') {\n'
      if versions[0] == version:
        impl += '    return ' + clsname + '_' + vstr + '_CppToC::UnwrapRaw(s);\n'
      else:
        impl += '    return ' + clsname + '_' + vstr + '_CppToC::UnwrapRaw(reinterpret_cast<' + cls.get_capi_name(
            version) + '*>(s));\n'
      impl += '  }\n'

    impl += '  ' + notreached + '\n'+ \
            '}\n\n' + \
            'CefBaseScoped* ' + typename + '_GetWrapper('+ structname + '* s) {\n' + \
            '  const int version = cef_api_version();\n'

    for version in rversions:
      vstr = str(version)
      impl += '  if (version >= ' + vstr + ') {\n'
      if versions[0] == version:
        impl += '    return ' + clsname + '_' + vstr + '_CppToC::GetWrapper(s);\n'
      else:
        impl += '    return ' + clsname + '_' + vstr + '_CppToC::GetWrapper(reinterpret_cast<' + cls.get_capi_name(
            version) + '*>(s));\n'
      impl += '  }\n'

    impl += '  ' + notreached + '\n'+ \
            '}\n'
  else:
    impl += structname + '* ' + typename + '_Wrap(CefRefPtr<' + clsname + '> c) {\n' + \
            '  const int version = cef_api_version();\n'

    for version in rversions:
      vstr = str(version)
      impl += '  if (version >= ' + vstr + ') {\n'
      if versions[0] == version:
        impl += '    return ' + clsname + '_' + vstr + '_CppToC::Wrap(c);\n'
      else:
        impl += '    return reinterpret_cast<' + structname + '*>(' + clsname + '_' + vstr + '_CppToC::Wrap(c));\n'
      impl += '  }\n'

    impl += '  ' + notreached + '\n'+ \
            '}\n\n' + \
            'CefRefPtr<' + clsname + '> ' + typename + '_Unwrap('+ structname + '* s) {\n' + \
            '  const int version = cef_api_version();\n'

    for version in rversions:
      vstr = str(version)
      impl += '  if (version >= ' + vstr + ') {\n'
      if versions[0] == version:
        impl += '    return ' + clsname + '_' + vstr + '_CppToC::Unwrap(s);\n'
      else:
        impl += '    return ' + clsname + '_' + vstr + '_CppToC::Unwrap(reinterpret_cast<' + cls.get_capi_name(
            version) + '*>(s));\n'
      impl += '  }\n'

    impl += '  ' + notreached + '\n'+ \
            '}\n'

  return impl + '\n'


def _version_finder(header, version, name, as_cpp=False):
  assert version is None or isinstance(version, int), version
  assert name[-1] != '*', name

  if as_cpp:
    cls = header.get_class(name)
    if cls is None:
      return name
    return cls.get_name(version=version)

  cls = header.get_capi_class(name)
  if not cls is None:
    return cls.get_capi_name(first_version=True)
  return name


def make_cpptoc_class_impl(header, clsname, impl):
  # structure names that have already been defined
  defined_names = header.get_defined_structs()

  # retrieve the class and populate the defined names
  cls = header.get_class(clsname)
  if cls is None:
    raise Exception('Class does not exist: ' + clsname)

  prefixname = get_capi_name(clsname[3:], False)

  base_class_name = header.get_base_class_name(clsname)
  base_scoped = True if base_class_name == 'CefBaseScoped' else False
  if base_scoped:
    template_class = 'CefCppToCScoped'
  else:
    template_class = 'CefCppToCRefCounted'

  with_versions = cls.is_library_side()
  versions = list(cls.get_all_versions()) if with_versions else (None,)

  # retrieve the existing static function implementations
  existing_static = get_function_impls(impl, 'CEF_EXPORT')

  # retrieve the existing virtual function implementations
  existing_virtual = get_function_impls(impl, 'CEF_CALLBACK')

  staticout = virtualout = ''
  customized = False
  first = True
  idx = 0

  for version in versions:
    version_finder = functools.partial(_version_finder, header,
                                       version) if with_versions else None
    defined_names.append(cls.get_capi_name(version=version))

    if first:
      first = False

      # generate static functions
      staticimpl, scustomized = make_cpptoc_function_impl(
          cls,
          cls.get_static_funcs(), existing_static, None, None, defined_names,
          base_scoped, version, version_finder)
      if len(staticimpl) > 0:
        staticout += '// GLOBAL FUNCTIONS - Body may be edited by hand.\n\n' + staticimpl
      if scustomized:
        customized = True

      if len(versions) > 1:
        staticout += '// HELPER FUNCTIONS - Do not edit by hand.\n\n'
        staticout += make_cpptoc_version_wrappers(header, cls, base_scoped,
                                                  versions)

    comment = '' if version is None else (' FOR VERSION %d' % version)

    suffixname = str(version) if (len(versions) > 1 and version > 0) else None

    # generate virtual functions
    virtualimpl, vcustomized = make_cpptoc_virtual_function_impl(
        header, cls, existing_virtual, prefixname, suffixname, defined_names,
        base_scoped, version, version_finder)
    if len(virtualimpl) > 0:
      virtualout += 'namespace {\n\n// MEMBER FUNCTIONS' + comment + ' - Body may be edited by hand.\n\n' + \
                    virtualimpl + '}  // namespace\n\n'
    if vcustomized:
      customized = True

    # any derived classes can be unwrapped
    unwrapderived = make_cpptoc_unwrap_derived(header, cls, base_scoped,
                                               version)

    capiname = cls.get_capi_name(version=version)
    typename = cls.get_name(version=version) + 'CppToC'

    const =  '// CONSTRUCTOR' + comment + ' - Do not edit by hand.\n\n'+ \
             typename+'::'+typename+'() {\n'

    if not version is None:
      if idx < len(versions) - 1:
        condition = 'version < %d || version >= %d' % (version, versions[idx
                                                                         + 1])
      else:
        condition = 'version < %d' % version

      const += '  const int version = cef_api_version();\n' + \
               '  LOG_IF(FATAL, ' + condition + ') << __func__ << " called with invalid version " << version;\n\n'

    const += make_cpptoc_virtual_function_assignment(header, cls, prefixname,
                                                     suffixname, defined_names,
                                                     version, version_finder)
    const += '}\n\n'+ \
             '// DESTRUCTOR' + comment + ' - Do not edit by hand.\n\n'+ \
             typename+'::~'+typename+'() {\n'

    if not cls.has_attrib('no_debugct_check') and not base_scoped:
      const += '  shutdown_checker::AssertNotShutdown();\n'

    const += '}\n\n'

    parent_sig = template_class + '<' + typename + ', ' + clsname + ', ' + capiname + '>'
    notreached = format_notreached(
        with_versions,
        '" called with unexpected class type " << type',
        default_retval='nullptr')

    if base_scoped:
      const += 'template<> CefOwnPtr<'+clsname+'> '+parent_sig+'::UnwrapDerivedOwn(CefWrapperType type, '+capiname+'* s) {\n' + \
               unwrapderived[0] + \
               '  ' + notreached + '\n'+ \
               '}\n\n' + \
               'template<> CefRawPtr<'+clsname+'> '+parent_sig+'::UnwrapDerivedRaw(CefWrapperType type, '+capiname+'* s) {\n' + \
               unwrapderived[1] + \
               '  ' + notreached + '\n'+ \
               '}\n\n'
    else:
      const += 'template<> CefRefPtr<'+clsname+'> '+parent_sig+'::UnwrapDerived(CefWrapperType type, '+capiname+'* s) {\n' + \
               unwrapderived + \
               '  ' + notreached + '\n'+ \
               '}\n\n'

    const += 'template<> CefWrapperType ' + parent_sig + '::kWrapperType = ' + get_wrapper_type_enum(
        clsname) + ';\n\n'

    virtualout += const
    idx += 1

  out = staticout + virtualout

  # determine what includes are required by identifying what translation
  # classes are being used
  includes = format_translation_includes(
      header,
      out + (unwrapderived[0] if base_scoped else unwrapderived),
      with_versions=with_versions)

  # build the final output
  result = get_copyright()

  result += includes + '\n'

  if with_versions:
    pre = post = ''
  else:
    pre, post = get_version_surround(cls, long=True)
    if len(pre) > 0:
      result += pre + '\n'

  result += out + '\n'

  if len(post) > 0:
    result += post + '\n'

  return (result, customized)


def make_cpptoc_global_impl(header, impl):
  # structure names that have already been defined
  defined_names = header.get_defined_structs()

  # retrieve the existing global function implementations
  existing = get_function_impls(impl, 'CEF_EXPORT')

  version_finder = functools.partial(_version_finder, header, None)

  # generate global functions
  impl, customized = make_cpptoc_function_impl(None,
                                               header.get_funcs(), existing,
                                               None, None, defined_names, False,
                                               None, version_finder)
  if len(impl) > 0:
    impl = '\n// GLOBAL FUNCTIONS - Body may be edited by hand.\n\n' + impl

  includes = ''

  # include required headers for global functions
  paths = set()
  for func in header.get_funcs():
    filename = func.get_file_name()
    paths.add('include/' + func.get_file_name())
    paths.add('include/capi/' + func.get_capi_file_name(versions=True))

  # determine what includes are required by identifying what translation
  # classes are being used
  includes += format_translation_includes(
      header, impl, with_versions=True, other_includes=paths)

  # build the final output
  result = get_copyright()

  result += includes + '\n' + impl

  return (result, customized)


def write_cpptoc_impl(header, clsname, dir):
  if clsname is None:
    # global file
    file = dir
  else:
    # class file
    # give the output file the same directory offset as the input file
    cls = header.get_class(clsname)
    dir = os.path.dirname(os.path.join(dir, cls.get_file_name()))
    file = os.path.join(dir, get_capi_name(clsname[3:], False) + '_cpptoc.cc')

  set_notify_context(file)

  if path_exists(file):
    oldcontents = read_file(file)
  else:
    oldcontents = ''

  if clsname is None:
    newcontents, customized = make_cpptoc_global_impl(header, oldcontents)
  else:
    newcontents, customized = make_cpptoc_class_impl(header, clsname,
                                                     oldcontents)

  set_notify_context(None)

  return (file, newcontents, customized)


# test the module
if __name__ == "__main__":
  import sys

  # verify that the correct number of command-line arguments are provided
  if len(sys.argv) < 4:
    sys.stderr.write('Usage: ' + sys.argv[0] +
                     ' <infile> <classname> <existing_impl>')
    sys.exit()

  # create the header object
  header = obj_header()
  header.add_file(sys.argv[1])

  # read the existing implementation file into memory
  try:
    with open(sys.argv[3], 'r') as f:
      data = f.read()
  except IOError as e:
    (errno, strerror) = e.args
    raise Exception('Failed to read file ' + sys.argv[3] + ': ' + strerror)
  else:
    f.close()

  # dump the result to stdout
  sys.stdout.write(make_cpptoc_class_impl(header, sys.argv[2], data))
